/**
 * Copyright (C) 2010 altuure <altuure [AT] gmail [DOT] com> http://www.altuure.com/projects/yagdao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.altuure.yagdao.common;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.altuure.yagdao.GenericDAO;
import com.altuure.yagdao.ObjectDAO;
import com.altuure.yagdao.YLimit;
import com.altuure.yagdao.YMethod;
import com.altuure.yagdao.YOrder;
import com.altuure.yagdao.YPage;
import com.altuure.yagdao.YParameter;

/**
 * common functionality for DAO factories.
 * @author altuure
 */
public abstract class GenericDAOFactory {
    private static final Log log = LogFactory.getLog(GenericDAOFactory.class);
    /**
     * target implementation base.
     */
    @SuppressWarnings("rawtypes")
    protected Class targetDAOImpl = null;

    /**
     * target interface.
     */
    @SuppressWarnings("rawtypes")
    protected Class targetInterfaces = null;

    /**
     * target object class for DAO.
     */
    @SuppressWarnings("rawtypes")
    protected Class objectClass;

    /**
     * fast property util for object class. see {@link #objectClass}
     */
    protected FastPropertyUtil propertyUtil;

    /**
     * dao instance class.
     */
    @SuppressWarnings("rawtypes")
    protected Class daoClass;

    /**
     * handle abstract calls and route calls.
     * @param obj proxy instance
     * @param method abstract method
     * @param args method args
     * @return return value
     * @throws Throwable
     */
    protected Object invokeInner(Object obj, Method method, Object[] args) throws Throwable {
        YMethod annotation = method.getAnnotation(YMethod.class);
        if (annotation == null) {
            log.info("GenericHibernateDAOFactory.no handler" + method.getName());
            throw new UnsupportedOperationException("no annotation is defined for method:" + method.getName());

        }
        switch (annotation.type()) {
        case SAVE:
            return saveMethod(obj, method, args);
        case UPDATE:
            return updateMethod(obj, method, args);
        case DELETE:
            return deleteMethod(obj, method, args);
        case QUERY:
            return queryMethod(obj, method, args);
        case CRITERIA:
            return criteriaMethod(obj, method, args);
        case COUNT:
            return countMethod(obj, method, args);

        default:
            throw new UnsupportedOperationException("unsupported annotaion:" + annotation.type());
        }

    }

    /*** try to initialize the objectClass. */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public void initObjectClass() {
        if (objectClass != null) {
            return;
        }
        if (targetInterfaces != null) {
            objectClass = (Class) Utils.getGenericDefiniton(targetInterfaces, GenericDAO.class, 0);
        }
        if (targetInterfaces != null) {
            ObjectDAO annotation = (ObjectDAO) targetInterfaces.getAnnotation(ObjectDAO.class);
            if (annotation != null)
                objectClass = annotation.value();
        }
        propertyUtil = new FastPropertyUtil(objectClass);
    }

    /**
     * getter for targetDAOImpl.
     * @return targetDAOImpl
     */
    @SuppressWarnings("rawtypes")
    public Class getTargetDAOImpl() {
        return targetDAOImpl;
    }

    /**
     * set target Imple.
     * @param targetDAOImpl
     */
    @SuppressWarnings("rawtypes")
    public void setTargetDAOImpl(Class targetDAOImpl) {
        this.targetDAOImpl = targetDAOImpl;
    }

    /**
     * get target Interface class.
     * @return targetInterfaces
     */
    @SuppressWarnings("rawtypes")
    public Class getTargetInterfaces() {
        return targetInterfaces;
    }

    /**
     * set targetInterface class.
     * @param targetInterfaces
     */
    @SuppressWarnings("rawtypes")
    public void setTargetInterfaces(Class targetInterfaces) {
        this.targetInterfaces = targetInterfaces;
    }

    /**
     * count method handler.
     * @see com.altuure.yagdao.YMethodType#COUNT
     * @param obj proxy object
     * @param method method
     * @param args arguments
     * @return result
     * @throws Throwable
     */
    protected abstract Object countMethod(Object obj, Method method, Object[] args) throws Throwable;

    /**
     * CRITERIA method handler.
     * @see com.altuure.yagdao.YMethodType#CRITERIA
     * @param obj proxy object
     * @param method method
     * @param args arguments
     * @return result
     * @throws Throwable
     */
    protected abstract Object criteriaMethod(Object obj, Method method, Object[] args) throws Throwable;

    /**
     * QUERY method handler.
     * @see com.altuure.yagdao.YMethodType#QUERY
     * @param obj proxy object
     * @param method method
     * @param args arguments
     * @return result
     * @throws Throwable
     */
    protected abstract Object queryMethod(Object obj, Method method, Object[] args) throws Throwable;

    /**
     * DELETE method handler.
     * @see com.altuure.yagdao.YMethodType#DELETE
     * @param obj proxy object
     * @param method method
     * @param args arguments
     * @return result
     * @throws Throwable
     */
    protected abstract Object deleteMethod(Object obj, Method method, Object[] args) throws Throwable;

    /**
     * update method handler.
     * @see com.altuure.yagdao.YMethodType#UPDATE
     * @param obj proxy object
     * @param method method
     * @param args arguments
     * @return result
     * @throws Throwable
     */
    protected abstract Object updateMethod(Object obj, Method method, Object[] args) throws Throwable;

    /**
     * SAVE method handler.
     * @see com.altuure.yagdao.YMethodType#SAVE
     * @param obj proxy object
     * @param method method
     * @param args arguments
     * @return result
     * @throws Throwable
     */
    protected abstract Object saveMethod(Object obj, Method method, Object[] args) throws Throwable;

    /**
     * set values for given object.
     * @param o object to be
     * @param method method with annotations
     * @param args arguments
     * @throws IllegalAccessException
     * @throws InvocationTargetException
     */
    protected void setValues(Object o, Method method, Object[] args) throws IllegalAccessException,
            InvocationTargetException {
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            Annotation[] parameterAnnotation = parameterAnnotations[i];
            if (parameterAnnotation == null)
                continue;
            for (Annotation annotation1 : parameterAnnotation) {
                if (annotation1 instanceof YParameter) {
                    YParameter daoMethodParameter = (YParameter) annotation1;
                    String s = daoMethodParameter.value();
                    propertyUtil.set(o, s, args[i]);
                }
            }
        }
    }

    /**
     * init proxy handler.
     * @return call back method.
     */
    public InvocationHandler initHandler() {
        if (daoClass != null)
            return null;
        InvocationHandler h = new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return invokeInner(proxy, method, args);
            }
        };
        return h;

    }

    /**
     * utility method to find DAO page from arguments.
     * @param args arguments
     * @return null if not found.
     */
    public YPage findDAOPage(Method method, Object[] args) {
        YPage paging = null;
        for (Object arg : args) {
            if (arg == null)
                continue;
            if (arg instanceof YPage) {
                paging = (YPage) arg;
                break;

            }
            if (arg instanceof YLimit) {
                paging = YPage.copy((YLimit) arg);
                break;

            }
        }

        return initStaticOrder(method, paging);
    }

    /**
     * retrive ordering from method if not found in the arguments
     * @param method executed method
     * @param page ordering page
     * @return new DAO Page if any value found
     */
    protected YPage initStaticOrder(Method method, YPage page) {
        if (page != null && page.isOrdered())
            return page;
        if (method == null)
            return page;
        YMethod annotation = method.getAnnotation(YMethod.class);

        if (annotation == null || annotation.orderBy() == null || annotation.orderBy().trim().length() == 0)
            return page;
        String orderBy = annotation.orderBy();
        ArrayList<YOrder> orders = Utils.parseOrderString(orderBy);
        if (page == null)
            page = new YPage();
        page.setOrders(orders);
        return page;
    }
}
